// ----------------------------------------------------------------------------------
// Microsoft Developer & Platform Evangelism
// 
// Copyright (c) Microsoft Corporation. All rights reserved.
// 
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES 
// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
// ----------------------------------------------------------------------------------
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
// ----------------------------------------------------------------------------------

using System;
using System.Net;
using System.Net.Browser;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Threading;

namespace SL.IdentityModel.Protocols.WSTrust
{
    /// <summary>
    /// class is used to make ws-trust RequestSecurityToken requests using trust13, and only the issue action
    /// </summary>
    public class WSTrustClient : ClientBase<IWSTrustContract>, IWSTrustContract
    {
        /// <summary>
        /// Class is used by the WSTrustClient to make WSTrust Issue requests
        /// </summary>
        private class WSTrustClientChannel : ChannelBase<IWSTrustContract>, IWSTrustContract
        {
            public WSTrustClientChannel( System.ServiceModel.ClientBase<IWSTrustContract> client ) :
                base( client )
            { }

            public System.IAsyncResult BeginIssue( Message message, System.AsyncCallback callback, object asyncState )
            {
                object[] _args = new object[1];
                _args[0] = message;
                System.IAsyncResult _result = base.BeginInvoke( "Issue", _args, callback, asyncState );
                return _result;
            }

            public Message EndIssue( System.IAsyncResult result )
            {
                object[] _args = new object[0];
                Message resultmsg = null;

                resultmsg = ( (Message) ( base.EndInvoke( "Issue", _args, result ) ) );

                return resultmsg;
            }
        }

        private const string IssueAction = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue";

        private BeginOperationDelegate _onBeginIssueDelegate;
        private EndOperationDelegate _onEndIssueDelegate;
        private SendOrPostCallback _onIssueCompletedDelegate;

        public event System.EventHandler<IssueCompletedEventArgs> IssueCompleted;

        /// <summary>
        /// Creates an instance of the class.
        /// 
        /// Bindings dervived from WSTrustBinding create bindings compatable with specified credential types
        /// 
        /// WSTrustBindingUserNamaMixed  -> UsernameCredentials
        /// WSTrustBindignWindows -> WindowsCredentials
        /// 
        /// </summary>
        /// <param name="binding">binding to be use for contacting the security token service</param>
        /// <param name="remoteAddress">endoint of securitytoken service</param>
        /// <param name="credentials">credential used to authentiate to the security token service</param>
        public WSTrustClient( Binding binding, EndpointAddress remoteAddress, IRequestCredentials credentials )
            : base( binding, remoteAddress )
        {
            if ( null == credentials )
            {
                throw new ArgumentNullException( "credentials" );
            }

            if ( credentials is UsernameCredentials )
            {
                UsernameCredentials usernname = credentials as UsernameCredentials;
                base.ChannelFactory.Credentials.UserName.UserName = usernname.Username;
                base.ChannelFactory.Credentials.UserName.Password = usernname.Password;
            }
            else if ( credentials is WindowsCredentials )
            { }
            else
            {
                throw new ArgumentOutOfRangeException( "credentials", "type was not expected" );
            }
        }

        /// <summary>
        /// Starts async service call to request security token
        /// once complete, eventhandler IssueCompleted is called
        /// </summary>
        /// <param name="rst">Provides RequestSecurityToken to use in the request</param>
        public void IssueAsync( RequestSecurityToken rst )
        {
            this.IssueAsync( rst, null );
        }

        /// <summary>
        /// Starts async service call to request security token
        /// once complete, eventhandler IssueCompleted is called
        /// </summary>
        /// <param name="rst">Provides RequestSecurityToken to use in the request</param>
        /// <param name="userState">userstate to be passe to async call</param>
        public void IssueAsync( RequestSecurityToken rst, object userState )
        {
            if ( null == _onBeginIssueDelegate )
            {
                _onBeginIssueDelegate = new BeginOperationDelegate( this.OnBeginIssue );
            }
            if ( null == _onEndIssueDelegate )
            {
                _onEndIssueDelegate = new EndOperationDelegate( this.OnEndIssue );
            }
            if ( null == _onIssueCompletedDelegate )
            {
                _onIssueCompletedDelegate = new SendOrPostCallback( this.OnIssueCompleted );
            }
            object[] newUserState = new object[2];
            newUserState[0] = rst;
            newUserState[1] = userState;

            base.InvokeAsync( _onBeginIssueDelegate, new object[] {
                        rst }, _onEndIssueDelegate, _onIssueCompletedDelegate, newUserState );
        }


        /// <summary>
        /// Creates WSTrust channel
        /// </summary>
        /// <returns></returns>
        protected override IWSTrustContract CreateChannel()
        {
            return new WSTrustClientChannel( this );
        }

        /// <summary>
        /// Given a RequestSecurityToken object, creates the message for the SOAP call
        /// </summary>
        /// <param name="request">RequestSecurityToken</param>
        /// <returns>Message</returns>
        private Message BuildRequestAsMessage( RequestSecurityToken request )
        {
            return Message.CreateMessage( base.Endpoint.Binding.MessageVersion ?? MessageVersion.Default,
                                        IssueAction,
                                        (BodyWriter) new WSTrustRequestBodyWriter( request ) );
        }

        private IAsyncResult OnBeginIssue( object[] inValues, System.AsyncCallback callback, object asyncState )
        {
            RequestSecurityToken rst = inValues[0] as RequestSecurityToken;

            Message request = BuildRequestAsMessage( rst );
            return this.Channel.BeginIssue( request, callback, asyncState );
        }

        private object[] OnEndIssue( System.IAsyncResult result )
        {
            Message retVal = ( (WSTrustClient) ( this ) ).EndIssue( result );

            WSTrust13ResponseSerializer rstrSerializer = new WSTrust13ResponseSerializer();
            RequestSecurityTokenResponse rstr = null;

            try
            {
                rstr = rstrSerializer.ReadXml( retVal as Message );
            }
            catch ( ArgumentNullException )
            {
                throw new FaultException( MessageFault.CreateFault( retVal, 2000 ), "http://www.w3.org/2005/08/addressing/soap/fault" );
            }

            return new object[] { rstr };
        }

        /// <summary>
        /// Is called once async operation is complete
        /// calls eventhandler IssueCompleted
        /// </summary>
        /// <param name="state">userstate</param>
        private void OnIssueCompleted( object state )
        {
            if ( null != IssueCompleted )
            {
                InvokeAsyncCompletedEventArgs e = ( (InvokeAsyncCompletedEventArgs) ( state ) );
                object[] results = e.Results as object[];
                if ( null != results && results[0] is RequestSecurityTokenResponse )
                {
                    RequestSecurityTokenResponse rstr = results[0] as RequestSecurityTokenResponse;
                    rstr.RST = ( (object[]) e.UserState )[0] as RequestSecurityToken;
                }
                IssueCompleted( this, new IssueCompletedEventArgs( e.Results, e.Error, e.Cancelled, ( (object[]) e.UserState )[1] ) );
            }
        }

        #region IWSTrustClient

        /// <summary>
        /// begins the async call for token issuance
        /// </summary>
        /// <param name="message">XML of message to be used in teh call</param>
        /// <param name="callback">Method to call once async call is complete</param>
        /// <param name="asyncState">userstate passed into call</param>
        /// <returns></returns>
        public IAsyncResult BeginIssue( Message message, AsyncCallback callback, object asyncState )
        {
            return asyncState as IAsyncResult;
        }

        /// <summary>
        /// Called on completion of async call
        /// </summary>
        /// <param name="asyncResult"></param>
        /// <returns></returns>
        public Message EndIssue( IAsyncResult asyncResult )
        {
            return base.Channel.EndIssue( asyncResult );
        }

        #endregion

    }
}